#include "WordFilter.h"
#include "datrie/trie.h"
#include "utf8cpp/utf8.h"
#include "System.h"

WordFilter::WordFilter()
: m_fltChar('*')
, m_trieInst(NULL)
{
}

WordFilter::~WordFilter()
{
	if (m_trieInst != NULL) {
		trie_free(m_trieInst);
	}
	for (auto& pair : m_obsoleteInsts) {
		trie_free(pair.first);
	}
}

Trie* WordFilter::NewTrieInst()
{
	AlphaMap* alpha_map = alpha_map_new();
	alpha_map_add_range(alpha_map, 1, 255);
	Trie* trie = trie_new(alpha_map);
	alpha_map_free(alpha_map);
	return trie;
}

Trie* WordFilter::CloneTrieInst(Trie* trieInst)
{
	FILE* file = std::tmpfile();
	trie_fwrite(trieInst, file);
	std::fseek(file, 0, SEEK_SET);
	Trie* trie = trie_fread(file);
	std::fclose(file);
	return trie;
}

void WordFilter::Init()
{
	m_trieInst = NewTrieInst();
}

void WordFilter::FreeObsoleteInsts(time_t expireTime)
{
	std::lock_guard<std::mutex> lock(m_mutex);
	for (auto itr = m_obsoleteInsts.begin(); itr != m_obsoleteInsts.end();) {
		if (itr->second <= expireTime) {
			trie_free(itr->first);
			itr = m_obsoleteInsts.erase(itr);
		} else {
			break;
		}
	}
}

void WordFilter::SetFilterChar(char ch)
{
	m_fltChar = ch;
}

static void Word2Key(const std::string_view& word, std::vector<AlphaChar>& key)
{
	key.clear();
	key.reserve(word.size() + 1);
	for (auto ch : word) {
		key.push_back(u8(ch));
	}
	key.push_back(0);
}

void WordFilter::AddWords(const std::vector<std::string_view>& words)
{
	std::lock_guard<std::mutex> lock(m_mutex);
	auto trie = CloneTrieInst(m_trieInst);
	std::vector<AlphaChar> key;
	for (auto& word : words) {
		Word2Key(word, key);
		trie_store(trie, key.data(), 0);
	}
	m_obsoleteInsts.emplace_back(m_trieInst, GET_UNIX_TIME);
	m_trieInst = trie;
}

void WordFilter::RemoveWords(const std::vector<std::string_view>& words)
{
	std::lock_guard<std::mutex> lock(m_mutex);
	auto trie = CloneTrieInst(m_trieInst);
	std::vector<AlphaChar> key;
	for (auto& word : words) {
		Word2Key(word, key);
		trie_delete(trie, key.data());
	}
	m_obsoleteInsts.emplace_back(m_trieInst, GET_UNIX_TIME);
	m_trieInst = trie;
}

void WordFilter::ReplaceWords(const std::vector<std::string_view>& words)
{
	std::lock_guard<std::mutex> lock(m_mutex);
	auto trie = NewTrieInst();
	std::vector<AlphaChar> key;
	for (auto& word : words) {
		Word2Key(word, key);
		trie_store(trie, key.data(), 0);
	}
	m_obsoleteInsts.emplace_back(m_trieInst, GET_UNIX_TIME);
	m_trieInst = trie;
}

bool WordFilter::IsWordFilter(const std::string_view& str) const
{
	bool isFound = false;
	auto state = trie_root(m_trieInst);
	auto itrx = str.begin(), end = str.end();
	for (; itrx < end; utf8::next(itrx, end)) {
		for (auto itr = itrx, next = itrx; itr < end; itr = next) {
			for (utf8::next(next, end); itr < next; ++itr) {
				if (!trie_state_walk(state, u8(*itr))) {
					break;
				}
			}
			if (itr == next) {
				if (trie_state_is_terminal(state)) {
					isFound = true;
					break;
				}
			} else {
				break;
			}
		}
		if (!isFound) {
			trie_state_rewind(state);
		} else {
			break;
		}
	}
	trie_state_free(state);
	return isFound;
}

std::string WordFilter::DoWordFilter(const std::string_view& str) const
{
	std::string rstr;
	rstr.reserve(str.size());
	auto state = trie_root(m_trieInst);
	auto found = str.begin(), end = str.end();
	for (auto itrx = found; itrx < end; itrx = found) {
		for (auto itr = itrx, next = itrx; itr < end; itr = next) {
			for (utf8::next(next, end); itr < next; ++itr) {
				if (!trie_state_walk(state, u8(*itr))) {
					break;
				}
			}
			if (itr == next) {
				if (trie_state_is_terminal(state)) {
					found = itr;
				}
			} else {
				break;
			}
		}
		if (itrx == found) {
			utf8::next(found, end);
			rstr.append(itrx, found);
		} else {
			rstr.append(utf8::distance(itrx, found), m_fltChar);
		}
		trie_state_rewind(state);
	}
	trie_state_free(state);
	return rstr;
}
